package net.oschina.bilbodai.common.beanutil.bean.core;

import net.oschina.bilbodai.common.beanutil.bean.BeanDumpUtil;
import net.oschina.bilbodai.common.beanutil.bean.PropertyDumpType;
import net.oschina.bilbodai.common.beanutil.bean.annotation.DumpProperty;
import net.oschina.bilbodai.common.beanutil.bean.type.NullType;
import net.oschina.bilbodai.common.beanutil.util.CollectionUtils;
import net.oschina.bilbodai.common.beanutil.util.StringUtil;
import net.oschina.bilbodai.common.beanutil.util.TypeUtil;
import org.objectweb.asm.Type;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * @author daimao  Date: 16/5/28 Time: 上午11:24
 * @version $Id$
 */
public class PropertyInfo {

    /**
     * 属性所在的类
     */
    private final Class<?> ownerType;

    private final String[] profiles;

    private final String name;

    private final Field field;

    private final DumpProperty dumpProperty;

    private final Method getter;

    private final Method setter;

    private final boolean sorted;

    public PropertyInfo(Class<?> ownerType) {
        this(ownerType, null);
    }

    public PropertyInfo(Class<?> ownerType, Field field) {
        this(ownerType, field, null);
    }

    public PropertyInfo(Class<?> ownerType, Field field, DumpProperty dumpProperty) {
        this.ownerType = ownerType;
        this.dumpProperty = dumpProperty;
        this.field = field;
        this.sorted = dumpProperty != null && dumpProperty.sorted();
        this.profiles = findProfiles(dumpProperty);
        this.name = findName(field, dumpProperty);
        this.getter = field == null ? null : TypeUtil.getGetterMethod(field.getName(), ownerType);
        this.setter = field == null ? null : TypeUtil.getSetterMethod(field.getName(), field.getType(), ownerType);
    }

    public String[] getProfiles() {
        return profiles;
    }

    public DumpProperty getDumpProperty() {
        return dumpProperty;
    }

    public Class<?> getOwnerType() {
        return ownerType;
    }

    public String getName() {
        return name;
    }

    public Field getField() {
        return field;
    }

    public Class<?> getFieldType() {
        if (field == null)
            return null;
        return field.getType();
    }

    public Method getGetter() {
        return getter;
    }

    public Method getSetter() {
        return setter;
    }

    private String findName(Field field, DumpProperty dumpProperty) {
        if (dumpProperty == null)
            return field.getName();
        if (StringUtil.isEmpty(dumpProperty.name()))
            return field.getName();
        return dumpProperty.name();
    }

    private String[] findProfiles(DumpProperty dumpProperty) {
        if (dumpProperty == null || CollectionUtils.isEmpty(dumpProperty.profiles())) {
            return new String[] {
                    BeanDumpUtil.DEFAULT_PROFILE_NAME };
        }
        return dumpProperty.profiles();
    }

    public boolean isDumpOverride() {
        if (dumpProperty == null)
            return false;
        return dumpProperty.forceDump();
    }

    public boolean isDumpOverrideCustomSet() {
        return dumpProperty != null;
    }

    public boolean isPrimitive() {
        return this.field.getType().isPrimitive();
    }

    public boolean usingAttrPathQuery() {
        if (dumpProperty == null)
            return false;
        return StringUtil.isNotBlank(dumpProperty.attrPath());
    }

    public Class<?> getAttrQueryType() {
        if (dumpProperty == null)
            return NullType.class;
        return dumpProperty.attrType();
    }

    public String getAttrQueryPath() {
        if (dumpProperty == null)
            return null;
        return dumpProperty.attrPath();
    }

    public String getGetterName() {
        if (getter == null)
            return null;
        return getter.getName();
    }

    public String getSetterName() {
        if (setter == null)
            return null;
        return setter.getName();
    }

    public String getGetterDescriptor() {
        if (getter == null)
            return null;
        return Type.getMethodDescriptor(getter);
    }

    public String getSetterDescriptor() {
        if (setter == null)
            return null;
        return Type.getMethodDescriptor(setter);
    }

    public Class<?> getKeyType() {
        if (!Map.class.isAssignableFrom(getFieldType()))
            return NullType.class;
        List<Class<?>> genericTypes = TypeUtil.getGenericTypes(field);
        if (!CollectionUtils.isEmpty(genericTypes))
            return genericTypes.get(0);
        return NullType.class;
    }

    public Class<?> getComponentType() {
        List<Class<?>> genericTypes = TypeUtil.getGenericTypes(field);
        if (CollectionUtils.isEmpty(genericTypes))
            return NullType.class;
        if (Collection.class.isAssignableFrom(getFieldType()))
            return genericTypes.get(0);
        if (Map.class.isAssignableFrom(getFieldType()))
            return genericTypes.get(1);
        return NullType.class;
    }

    public Class<?> getSetSrcComponentType() {
        if (dumpProperty == null)
            return NullType.class;
        return dumpProperty.srcComponentType();
    }

    public Class<?> getSetTargetComponentType() {
        if (dumpProperty == null)
            return NullType.class;
        return dumpProperty.targetComponentType();
    }

    public Class<?> getSetSrcKeyType() {
        if (dumpProperty == null)
            return NullType.class;
        return dumpProperty.srcKeyType();
    }

    public Class<?> getSetTargetKeyType() {
        if (dumpProperty == null)
            return NullType.class;
        return dumpProperty.targetKeyType();
    }

    public Class<?> getCollectTypeImpl() {
        if (dumpProperty != null) {
            if (!dumpProperty.collectTypeImpl().equals(NullType.class))
                return dumpProperty.collectTypeImpl();
            if (dumpProperty.targetDumpType().isSet())
                return dumpProperty.targetDumpType().getTypeImpl();
            if (dumpProperty.srcDumpType().isSet())
                return dumpProperty.srcDumpType().getTypeImpl();
        }
        return CollectImplTypes.findProperCollectImplType(getFieldType());
    }

    public PropertyDumpType getSrcDumpType() {
        if (dumpProperty == null)
            return PropertyDumpType.NONE;
        return dumpProperty.srcDumpType();
    }

    public PropertyDumpType getTargetDumpType() {
        if (dumpProperty == null)
            return PropertyDumpType.NONE;
        return dumpProperty.targetDumpType();
    }

    public String getMapFieldName() {
        if (dumpProperty == null)
            return "";
        return dumpProperty.mapFieldName();
    }

    public boolean required() {
        if (dumpProperty == null)
            return false;
        return dumpProperty.required();
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("PropertyInfo{");
        sb.append("ownerType=").append(ownerType);
        sb.append(", name='").append(name).append('\'');
        sb.append(", field=").append(field);
        sb.append(", profiles=").append(Arrays.toString(profiles));
        sb.append('}');
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        PropertyInfo that = (PropertyInfo) o;

        return name != null ? name.equals(that.name) : that.name == null;

    }

    public boolean isSorted() {
        return sorted;
    }

    @Override
    public int hashCode() {
        return name != null ? name.hashCode() : 0;
    }
}